#include <iostream>
#include <streambuf>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>
#include <unistd.h>

class OutFileBuf : public std::streambuf
{
public:
    OutFileBuf(int fd, int size = 4096) : m_fd(fd), m_bufsize(size), m_buffer(new char[m_bufsize])
    {
        std::cout << "Calling " << __func__  << std::endl;
        setp(m_buffer, m_buffer+m_bufsize);
        std::cout << "Total Buffer size: " << epptr()-pbase() << std::endl;
        std::cout << "Current Buffer size: " << epptr()-pptr() << std::endl;
    }
    ~OutFileBuf()
    {
        std::cout << "Calling " << __func__  << std::endl;
        sync();
        delete [] m_buffer;
    }
protected:
    int_type overflow(int_type ch) override
    {
        std::cout << "Calling " << __func__  << std::endl;
        int len = 0;
        if(ch == traits_type::eof())
        {
            return traits_type::eof();
        }
        if(::write(m_fd, pbase(), m_bufsize)  != m_bufsize)
        {
            return traits_type::eof();
        }
        pbump(-m_bufsize);
        *pptr() = traits_type::to_char_type(ch);
        pbump(1);
        return  ch;
    }

    int sync() override
    {
        std::cout << "Calling " << __func__  << std::endl;
        int len = pptr()-pbase();
        if(len > 0)
        {
            if(::write(m_fd, pbase(), len)  != len)
            {
                return -1;
            }
        }
        return fdatasync(m_fd);
    }
    std::streambuf *setbuf(char_type *, std::streamsize) override
    {
        std::cerr << "Calling " << __func__  << " for OutFileBuf is undefined!"<< std::endl; 
        return nullptr;
    }
    pos_type seekoff(off_type, std::ios_base::seekdir,  std::ios_base::openmode) override
    {
        std::cerr << "Calling " << __func__  << " for OutFileBuf is undefined!"<< std::endl; 
        return pos_type(off_type(-1));
    }
    pos_type seekpos(pos_type, std::ios_base::openmode) override
    {
        std::cerr << "Calling " << __func__  << " for OutFileBuf is undefined!"<< std::endl; 
        return pos_type(off_type(-1));
    }
private:
    int m_fd;
    int m_bufsize;
    char *m_buffer;
};


class InFileBuf : public std::streambuf
{
public:
    InFileBuf(int fd, int size = 4096) : m_fd(fd), 
                                         m_bufsize(size), 
                                         m_buffer(new char[m_bufsize])
    {
        std::cout << "Calling " << __func__  << std::endl;
        setg(m_buffer, m_buffer+m_bufsize, m_buffer+m_bufsize);
        std::cout << "Total Buffer size: " << egptr()-eback() << std::endl;
        std::cout << "Current Buffer size: " << egptr()-gptr() << std::endl;
    }
    ~InFileBuf()
    {
        std::cout << "Calling " << __func__  << std::endl;
        delete [] m_buffer;
    }

protected:
    int_type underflow() override
    {
        std::cout << "Calling " << __func__  << std::endl;
        int n = ::read(m_fd, eback(), m_bufsize);
        if(n<=0) //0: 上次已读尽， -1：本次读取出错
        {
            return traits_type::eof();
        }
        if(n < m_bufsize) //本次读尽, 但没有填满buffer
        {
            memmove(eback()+m_bufsize-n, eback(), n);
        }
        gbump(-n);
        return traits_type::to_int_type(*gptr());
    }
    int sync() override
    {
        std::cerr << "Calling " << __func__  << " for InFileBuf is undefined!"<< std::endl; 
        return -1;
    }
    std::streambuf *setbuf(char_type *s, std::streamsize n) override
    {
        std::cerr << "Calling " << __func__  << " for InFileBuf is undefined!"<< std::endl; 
        return nullptr;
    }
    pos_type seekoff(off_type, std::ios_base::seekdir,  std::ios_base::openmode) override
    {
        std::cerr << "Calling " << __func__  << " for InFileBuf is undefined!"<< std::endl; 
        return pos_type(off_type(-1));
    }
    pos_type seekpos(pos_type, std::ios_base::openmode) override
    {
        std::cerr << "Calling " << __func__  << " for InFileBuf is undefined!"<< std::endl; 
        return pos_type(off_type(-1));
    }
private:
    int m_fd;
    int m_bufsize;
    char *m_buffer;
};

int main(int argc, char *argv[])
{
    int ifd = ::open(argv[0], O_RDONLY|O_CLOEXEC);
    InFileBuf inbuf(ifd);
    std::istream in(&inbuf);
    std::string outfile(argv[0]);
    auto pos = outfile.rfind("/");
    if(pos != std::string::npos)
    {
        outfile = outfile.substr(pos);
    }
    outfile = "/tmp/" + outfile + ".bak";
    std::cout << "Output file: " << outfile << std::endl;
    int ofd = ::open(outfile.c_str(), O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0700);
    OutFileBuf outbuf(ofd);
    std::ostream out(&outbuf);
    const int bufsize = 512;
    char buf[bufsize];
    int len = -1;
    do 
    {
        in.read(buf, bufsize);
        len = in.gcount();
        if(len > 0)
        {
            std::cout << "Writing data len: " << len << std::endl;
            out.write(buf, len);
        }
    } while(!in.eof() && in.good() && out.good());
    //注意这里不能close，需要等待OutFileBuf析构时将数据到文件后才能关闭
    //close(ifd);
    //close(ofd);
    return 0;
}
